/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.0 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Vertex array object tests
 *//*--------------------------------------------------------------------*/
#include "es3fVertexArrayObjectTests.hpp"

#include "gluShaderProgram.hpp"
#include "gluPixelTransfer.hpp"
#include "gluRenderContext.hpp"

#include "tcuTestLog.hpp"
#include "tcuImageCompare.hpp"
#include "tcuSurface.hpp"
#include "tcuRenderTarget.hpp"

#include "deRandom.hpp"
#include "deString.h"
#include "deMemory.h"

#include <vector>
#include <string>
#include <memory>

#include "glw.h"

using std::vector;
using std::string;

namespace deqp
{
namespace gles3
{
namespace Functional
{

namespace
{
struct Attribute
{
				Attribute (void);
	GLboolean	enabled;
	GLint		size;
	GLint		stride;
	GLenum		type;
	GLboolean	integer;
	GLint		divisor;
	GLint		offset;
	GLboolean	normalized;

	int			bufferNdx;
};

struct VertexArrayState
{
						VertexArrayState	(void);

	vector<Attribute>	attributes;
	int					elementArrayBuffer;
};

VertexArrayState::VertexArrayState (void)
	: elementArrayBuffer(-1)
{
}

Attribute::Attribute (void)
	: enabled		(GL_FALSE)
	, size			(1)
	, stride		(0)
	, type			(GL_FLOAT)
	, integer		(GL_FALSE)
	, divisor		(0)
	, offset		(0)
	, normalized	(GL_FALSE)
	, bufferNdx		(0)
{
}

struct BufferSpec
{
	int		count;
	int		size;
	int		componentCount;
	int		stride;
	int		offset;

	GLenum	type;

	int		intRangeMin;
	int		intRangeMax;

	float	floatRangeMin;
	float	floatRangeMax;
};

struct Spec
{
						Spec	(void);

	int					count;
	int					instances;
	bool				useDrawElements;
	GLenum				indexType;
	int					indexOffset;
	int					indexRangeMin;
	int					indexRangeMax;
	int					indexCount;
	VertexArrayState	state;
	VertexArrayState	vao;
	vector<BufferSpec>	buffers;
};

Spec::Spec (void)
	: count				(-1)
	, instances			(-1)
	, useDrawElements	(false)
	, indexType			(GL_NONE)
	, indexOffset		(-1)
	, indexRangeMin		(-1)
	, indexRangeMax		(-1)
	, indexCount		(-1)
{
}

} // anonymous

class VertexArrayObjectTest : public TestCase
{
public:

							VertexArrayObjectTest	(Context& context, const Spec& spec, const char* name, const char* description);
							~VertexArrayObjectTest	(void);
	virtual void			init					(void);
	virtual void			deinit					(void);
	virtual IterateResult	iterate					(void);

private:
	Spec					m_spec;
	tcu::TestLog&			m_log;
	vector<GLuint>			m_buffers;
	glu::ShaderProgram*		m_vaoProgram;
	glu::ShaderProgram*		m_stateProgram;
	de::Random				m_random;
	deUint8*				m_indices;

	void					logVertexArrayState (tcu::TestLog& log, const VertexArrayState& state, const std::string& msg);
	deUint8*				createRandomBufferData	(const BufferSpec& buffer);
	deUint8*				generateIndices			(void);
	glu::ShaderProgram*		createProgram			(const VertexArrayState& state);
	void					setState				(const VertexArrayState& state);
	void					render					(tcu::Surface& vaoResult, tcu::Surface& defaultResult);
	void					makeDrawCall			(const VertexArrayState& state);
	void					genReferences			(tcu::Surface& vaoRef, tcu::Surface& defaultRef);

							VertexArrayObjectTest	(const VertexArrayObjectTest&);
	VertexArrayObjectTest&	operator=				(const VertexArrayObjectTest&);
};

VertexArrayObjectTest::VertexArrayObjectTest (Context& context, const Spec& spec, const char* name, const char* description)
	: TestCase			(context, name, description)
	, m_spec			(spec)
	, m_log				(context.getTestContext().getLog())
	, m_vaoProgram		(NULL)
	, m_stateProgram	(NULL)
	, m_random			(deStringHash(name))
	, m_indices			(NULL)
{
	// Makes zero to zero mapping for buffers
	m_buffers.push_back(0);
}

VertexArrayObjectTest::~VertexArrayObjectTest (void)
{
}

void VertexArrayObjectTest::logVertexArrayState (tcu::TestLog& log, const VertexArrayState& state, const std::string& msg)
{
	std::stringstream message;

	message << msg << "\n";
	message << "GL_ELEMENT_ARRAY_BUFFER : " << state.elementArrayBuffer << "\n";

	for (int attribNdx = 0; attribNdx < (int)state.attributes.size(); attribNdx++)
	{
		message
		<< "attribute : " << attribNdx << "\n"
		<< "\tGL_VERTEX_ATTRIB_ARRAY_ENABLED : " << (state.attributes[attribNdx].enabled ? "GL_TRUE" : "GL_FALSE") <<  "\n"
		<< "\tGL_VERTEX_ATTRIB_ARRAY_SIZE : " << state.attributes[attribNdx].size <<  "\n"
		<< "\tGL_VERTEX_ATTRIB_ARRAY_STRIDE : " << state.attributes[attribNdx].stride <<  "\n"
		<< "\tGL_VERTEX_ATTRIB_ARRAY_TYPE : " << state.attributes[attribNdx].type <<  "\n"
		<< "\tGL_VERTEX_ATTRIB_ARRAY_NORMALIZED : " << (state.attributes[attribNdx].normalized ? "GL_TRUE" : "GL_FALSE") <<  "\n"
		<< "\tGL_VERTEX_ATTRIB_ARRAY_INTEGER : " << (state.attributes[attribNdx].integer ? "GL_TRUE" : "GL_FALSE") <<  "\n"
		<< "\tGL_VERTEX_ATTRIB_ARRAY_DIVISOR : " << state.attributes[attribNdx].divisor <<  "\n"
		<< "\tGL_VERTEX_ATTRIB_ARRAY_POINTER : " << state.attributes[attribNdx].offset <<  "\n"
		<< "\tGL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING : " << m_buffers[state.attributes[attribNdx].bufferNdx] <<  "\n";
	}
	log << tcu::TestLog::Message << message.str() << tcu::TestLog::EndMessage;
}


void VertexArrayObjectTest::init (void)
{
	// \note [mika] Index 0 is reserved for 0 buffer
	for (int bufferNdx = 0; bufferNdx < (int)m_spec.buffers.size(); bufferNdx++)
	{
		deUint8* data = createRandomBufferData(m_spec.buffers[bufferNdx]);

		try
		{
			GLuint buffer;
			GLU_CHECK_CALL(glGenBuffers(1, &buffer));
			m_buffers.push_back(buffer);

			GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, buffer));
			GLU_CHECK_CALL(glBufferData(GL_ARRAY_BUFFER, m_spec.buffers[bufferNdx].size, data, GL_DYNAMIC_DRAW));
			GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, 0));

		} catch (...) {
			delete[] data;
			throw;
		}

		delete[] data;
	}

	m_vaoProgram	= createProgram(m_spec.vao);
	m_log << tcu::TestLog::Message << "Program used with Vertex Array Object" << tcu::TestLog::EndMessage;
	m_log << *m_vaoProgram;
	m_stateProgram	= createProgram(m_spec.state);
	m_log << tcu::TestLog::Message << "Program used with Vertex Array State" << tcu::TestLog::EndMessage;
	m_log << *m_stateProgram;

	if (!m_vaoProgram->isOk() || !m_stateProgram->isOk())
		TCU_FAIL("Failed to compile shaders");

	if (m_spec.useDrawElements && (m_spec.vao.elementArrayBuffer == 0 || m_spec.state.elementArrayBuffer == 0))
		m_indices = generateIndices();
}

void VertexArrayObjectTest::deinit (void)
{
	GLU_CHECK_CALL(glDeleteBuffers((GLsizei)m_buffers.size(), &(m_buffers[0])));
	m_buffers.clear();
	delete m_vaoProgram;
	delete m_stateProgram;
	delete[] m_indices;
}

deUint8* VertexArrayObjectTest::generateIndices (void)
{
	int typeSize = 0;
	switch (m_spec.indexType)
	{
		case GL_UNSIGNED_INT:	typeSize = sizeof(GLuint);		break;
		case GL_UNSIGNED_SHORT:	typeSize = sizeof(GLushort);	break;
		case GL_UNSIGNED_BYTE:	typeSize = sizeof(GLubyte);		break;
		default:
			DE_ASSERT(false);
	}

	deUint8* indices = new deUint8[m_spec.indexCount * typeSize];

	for (int i = 0; i < m_spec.indexCount; i++)
	{
		deUint8* pos = indices + typeSize * i;

		switch (m_spec.indexType)
		{
			case GL_UNSIGNED_INT:
			{
				GLuint v = (GLuint)m_random.getInt(m_spec.indexRangeMin, m_spec.indexRangeMax);
				deMemcpy(pos, &v, sizeof(v));
				break;
			}

			case GL_UNSIGNED_SHORT:
			{
				GLushort v = (GLushort)m_random.getInt(m_spec.indexRangeMin, m_spec.indexRangeMax);
				deMemcpy(pos, &v, sizeof(v));
				break;
			}

			case GL_UNSIGNED_BYTE:
			{
				GLubyte v = (GLubyte)m_random.getInt(m_spec.indexRangeMin, m_spec.indexRangeMax);
				deMemcpy(pos, &v, sizeof(v));
				break;
			}

			default:
				DE_ASSERT(false);
		}
	}

	return indices;
}

deUint8* VertexArrayObjectTest::createRandomBufferData (const BufferSpec& buffer)
{
	deUint8* data = new deUint8[buffer.size];

	int stride;

	if (buffer.stride != 0)
	{
		stride = buffer.stride;
	}
	else
	{
		switch (buffer.type)
		{
			case GL_FLOAT:			stride = buffer.componentCount * (int)sizeof(GLfloat);	break;
			case GL_INT:			stride = buffer.componentCount * (int)sizeof(GLint);	break;
			case GL_UNSIGNED_INT:	stride = buffer.componentCount * (int)sizeof(GLuint);	break;
			case GL_SHORT:			stride = buffer.componentCount * (int)sizeof(GLshort);	break;
			case GL_UNSIGNED_SHORT:	stride = buffer.componentCount * (int)sizeof(GLushort);	break;
			case GL_BYTE:			stride = buffer.componentCount * (int)sizeof(GLbyte);	break;
			case GL_UNSIGNED_BYTE:	stride = buffer.componentCount * (int)sizeof(GLubyte);	break;

			default:
				stride = 0;
				DE_ASSERT(DE_FALSE);
		}
	}

	deUint8* itr = data;

	for (int pos = 0; pos < buffer.count; pos++)
	{
		deUint8* componentItr = itr;
		for (int componentNdx = 0; componentNdx < buffer.componentCount; componentNdx++)
		{
			switch (buffer.type)
			{
				case GL_FLOAT:
				{
					float v = buffer.floatRangeMin + (buffer.floatRangeMax - buffer.floatRangeMin) * m_random.getFloat();
					deMemcpy(componentItr, &v, sizeof(v));
					componentItr += sizeof(v);
					break;
				}

				case GL_INT:
				{
					GLint v = m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
					deMemcpy(componentItr, &v, sizeof(v));
					componentItr += sizeof(v);
					break;
				}

				case GL_UNSIGNED_INT:
				{
					GLuint v = m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
					deMemcpy(componentItr, &v, sizeof(v));
					componentItr += sizeof(v);
					break;
				}

				case GL_SHORT:
				{
					GLshort v = (GLshort)m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
					deMemcpy(componentItr, &v, sizeof(v));
					componentItr += sizeof(v);
					break;
				}

				case GL_UNSIGNED_SHORT:
				{
					GLushort v = (GLushort)m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
					deMemcpy(componentItr, &v, sizeof(v));
					componentItr += sizeof(v);
					break;
				}

				case GL_BYTE:
				{
					GLbyte v = (GLbyte)m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
					deMemcpy(componentItr, &v, sizeof(v));
					componentItr += sizeof(v);
					break;
				}

				case GL_UNSIGNED_BYTE:
				{
					GLubyte v = (GLubyte)m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
					deMemcpy(componentItr, &v, sizeof(v));
					componentItr += sizeof(v);
					break;
				}

				default:
					DE_ASSERT(false);
			}
		}

		itr += stride;
	}

	return data;
}

glu::ShaderProgram* VertexArrayObjectTest::createProgram (const VertexArrayState& state)
{
	std::stringstream vertexShaderStream;
	std::stringstream value;

	vertexShaderStream << "#version 300 es\n";

	for (int attribNdx = 0; attribNdx < (int)state.attributes.size(); attribNdx++)
	{
		if (state.attributes[attribNdx].integer)
			vertexShaderStream << "layout(location = " << attribNdx << ") in mediump ivec4 a_attrib" << attribNdx << ";\n";
		else
			vertexShaderStream << "layout(location = " << attribNdx << ") in mediump vec4 a_attrib" << attribNdx << ";\n";

		if (state.attributes[attribNdx].integer)
		{
			float scale = 0.0f;

			switch (state.attributes[0].type)
			{
				case GL_SHORT:			scale  = (1.0f/float((1u<<14)-1u));	break;
				case GL_UNSIGNED_SHORT:	scale  = (1.0f/float((1u<<15)-1u));	break;
				case GL_INT:			scale  = (1.0f/float((1u<<30)-1u));	break;
				case GL_UNSIGNED_INT:	scale  = (1.0f/float((1u<<31)-1u));	break;
				case GL_BYTE:			scale  = (1.0f/float((1u<<6)-1u));	break;
				case GL_UNSIGNED_BYTE:	scale  = (1.0f/float((1u<<7)-1u));	break;

				default:
					DE_ASSERT(DE_FALSE);
			}
			value << (attribNdx != 0 ? " + " : "" ) << scale << " * vec4(a_attrib" << attribNdx << ")";
		}
		else if (state.attributes[attribNdx].type != GL_FLOAT && !state.attributes[attribNdx].normalized)
		{
			float scale = 0.0f;

			switch (state.attributes[0].type)
			{
				case GL_SHORT:			scale  = (0.5f/float((1u<<14)-1u));	break;
				case GL_UNSIGNED_SHORT:	scale  = (0.5f/float((1u<<15)-1u));	break;
				case GL_INT:			scale  = (0.5f/float((1u<<30)-1u));	break;
				case GL_UNSIGNED_INT:	scale  = (0.5f/float((1u<<31)-1u));	break;
				case GL_BYTE:			scale  = (0.5f/float((1u<<6)-1u));	break;
				case GL_UNSIGNED_BYTE:	scale  = (0.5f/float((1u<<7)-1u));	break;

				default:
					DE_ASSERT(DE_FALSE);
			}
			value << (attribNdx != 0 ? " + " : "" ) << scale << " * a_attrib" << attribNdx;
		}
		else
			value << (attribNdx != 0 ? " + " : "" ) << "a_attrib" << attribNdx;
	}

	vertexShaderStream
		<< "out mediump vec4 v_value;\n"
		<< "void main (void)\n"
		<< "{\n"
		<< "\tv_value = " << value.str() << ";\n";

	if (state.attributes[0].integer)
	{
		float scale = 0.0f;

		switch (state.attributes[0].type)
		{
			case GL_SHORT:			scale  = (1.0f/float((1u<<14)-1u));	break;
			case GL_UNSIGNED_SHORT:	scale  = (1.0f/float((1u<<15)-1u));	break;
			case GL_INT:			scale  = (1.0f/float((1u<<30)-1u));	break;
			case GL_UNSIGNED_INT:	scale  = (1.0f/float((1u<<31)-1u));	break;
			case GL_BYTE:			scale  = (1.0f/float((1u<<6)-1u));	break;
			case GL_UNSIGNED_BYTE:	scale  = (1.0f/float((1u<<7)-1u));	break;

			default:
				DE_ASSERT(DE_FALSE);
		}

		vertexShaderStream
			<< "\tgl_Position = vec4(" << scale << " * " <<  "vec3(a_attrib0.xyz), 1.0);\n"
			<< "}";
	}
	else
	{
		if (state.attributes[0].normalized || state.attributes[0].type == GL_FLOAT)
		{
			vertexShaderStream
				<< "\tgl_Position = vec4(a_attrib0.xyz, 1.0);\n"
				<< "}";
		}
		else
		{
			float scale = 0.0f;

			switch (state.attributes[0].type)
			{
				case GL_SHORT:			scale  = (1.0f/float((1u<<14)-1u));	break;
				case GL_UNSIGNED_SHORT:	scale  = (1.0f/float((1u<<15)-1u));	break;
				case GL_INT:			scale  = (1.0f/float((1u<<30)-1u));	break;
				case GL_UNSIGNED_INT:	scale  = (1.0f/float((1u<<31)-1u));	break;
				case GL_BYTE:			scale  = (1.0f/float((1u<<6)-1u));	break;
				case GL_UNSIGNED_BYTE:	scale  = (1.0f/float((1u<<7)-1u));	break;

				default:
					DE_ASSERT(DE_FALSE);
			}

			scale *= 0.5f;

			vertexShaderStream
				<< "\tgl_Position = vec4(" << scale << " * " <<  "a_attrib0.xyz, 1.0);\n"
				<< "}";
		}
	}

	const char* fragmentShader =
	"#version 300 es\n"
	"in mediump vec4 v_value;\n"
	"layout(location = 0) out mediump vec4 fragColor;\n"
	"void main (void)\n"
	"{\n"
	"\tfragColor = vec4(v_value.xyz, 1.0);\n"
	"}";

	return new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderStream.str(), fragmentShader));
}

void VertexArrayObjectTest::setState (const VertexArrayState& state)
{
	GLU_CHECK_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_buffers[state.elementArrayBuffer]));

	for (int attribNdx = 0; attribNdx < (int)state.attributes.size(); attribNdx++)
	{
		GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, m_buffers[state.attributes[attribNdx].bufferNdx]));
		if (state.attributes[attribNdx].enabled)
			GLU_CHECK_CALL(glEnableVertexAttribArray(attribNdx));
		else
			GLU_CHECK_CALL(glDisableVertexAttribArray(attribNdx));

		if (state.attributes[attribNdx].integer)
			GLU_CHECK_CALL(glVertexAttribIPointer(attribNdx, state.attributes[attribNdx].size, state.attributes[attribNdx].type, state.attributes[attribNdx].stride, (const GLvoid*)((GLintptr)state.attributes[attribNdx].offset)));
		else
			GLU_CHECK_CALL(glVertexAttribPointer(attribNdx, state.attributes[attribNdx].size, state.attributes[attribNdx].type, state.attributes[attribNdx].normalized, state.attributes[attribNdx].stride, (const GLvoid*)((GLintptr)state.attributes[attribNdx].offset)));

		GLU_CHECK_CALL(glVertexAttribDivisor(attribNdx, state.attributes[attribNdx].divisor));
	}
}

void VertexArrayObjectTest::makeDrawCall (const VertexArrayState& state)
{
	GLU_CHECK_CALL(glClearColor(0.7f, 0.7f, 0.7f, 1.0f));
	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));

	for (int attribNdx = 0; attribNdx < (int)state.attributes.size(); attribNdx++)
	{
		if (state.attributes[attribNdx].integer)
			glVertexAttribI4i(attribNdx, 0, 0, 0, 1);
		else
			glVertexAttrib4f(attribNdx, 0.0f, 0.0f, 0.0f, 1.0f);
	}

	if (m_spec.useDrawElements)
	{
		if (state.elementArrayBuffer == 0)
		{
			if (m_spec.instances == 0)
				GLU_CHECK_CALL(glDrawElements(GL_TRIANGLES, m_spec.count, m_spec.indexType, m_indices));
			else
				GLU_CHECK_CALL(glDrawElementsInstanced(GL_TRIANGLES, m_spec.count, m_spec.indexType, m_indices, m_spec.instances));
		}
		else
		{
			if (m_spec.instances == 0)
				GLU_CHECK_CALL(glDrawElements(GL_TRIANGLES, m_spec.count, m_spec.indexType, (GLvoid*)((GLintptr)m_spec.indexOffset)));
			else
				GLU_CHECK_CALL(glDrawElementsInstanced(GL_TRIANGLES, m_spec.count, m_spec.indexType, (GLvoid*)((GLintptr)m_spec.indexOffset), m_spec.instances));
		}
	}
	else
	{
		if (m_spec.instances == 0)
			GLU_CHECK_CALL(glDrawArrays(GL_TRIANGLES, 0, m_spec.count));
		else
			GLU_CHECK_CALL(glDrawArraysInstanced(GL_TRIANGLES, 0, m_spec.count, m_spec.instances));
	}
}

void VertexArrayObjectTest::render (tcu::Surface& vaoResult, tcu::Surface& defaultResult)
{
	GLuint vao = 0;

	GLU_CHECK_CALL(glGenVertexArrays(1, &vao));
	GLU_CHECK_CALL(glBindVertexArray(vao));
	setState(m_spec.vao);
	GLU_CHECK_CALL(glBindVertexArray(0));

	setState(m_spec.state);

	GLU_CHECK_CALL(glBindVertexArray(vao));
	GLU_CHECK_CALL(glUseProgram(m_vaoProgram->getProgram()));
	makeDrawCall(m_spec.vao);
	glu::readPixels(m_context.getRenderContext(), 0, 0, vaoResult.getAccess());
	setState(m_spec.vao);
	GLU_CHECK_CALL(glBindVertexArray(0));

	GLU_CHECK_CALL(glUseProgram(m_stateProgram->getProgram()));
	makeDrawCall(m_spec.state);
	glu::readPixels(m_context.getRenderContext(), 0, 0, defaultResult.getAccess());
}

void VertexArrayObjectTest::genReferences (tcu::Surface& vaoRef, tcu::Surface& defaultRef)
{
	setState(m_spec.vao);
	GLU_CHECK_CALL(glUseProgram(m_vaoProgram->getProgram()));
	makeDrawCall(m_spec.vao);
	glu::readPixels(m_context.getRenderContext(), 0, 0, vaoRef.getAccess());

	setState(m_spec.state);
	GLU_CHECK_CALL(glUseProgram(m_stateProgram->getProgram()));
	makeDrawCall(m_spec.state);
	glu::readPixels(m_context.getRenderContext(), 0, 0, defaultRef.getAccess());
}

TestCase::IterateResult VertexArrayObjectTest::iterate (void)
{
	tcu::Surface	vaoReference	(m_context.getRenderContext().getRenderTarget().getWidth(), m_context.getRenderContext().getRenderTarget().getHeight());
	tcu::Surface	stateReference	(m_context.getRenderContext().getRenderTarget().getWidth(), m_context.getRenderContext().getRenderTarget().getHeight());

	tcu::Surface	vaoResult		(m_context.getRenderContext().getRenderTarget().getWidth(), m_context.getRenderContext().getRenderTarget().getHeight());
	tcu::Surface	stateResult		(m_context.getRenderContext().getRenderTarget().getWidth(), m_context.getRenderContext().getRenderTarget().getHeight());

	bool			isOk;

	logVertexArrayState(m_log, m_spec.vao, "Vertex Array Object State");
	logVertexArrayState(m_log, m_spec.state, "OpenGL Vertex Array State");
	genReferences(stateReference, vaoReference);
	render(stateResult, vaoResult);

	isOk = tcu::pixelThresholdCompare (m_log, "Results", "Comparison result from rendering with Vertex Array State", stateReference, stateResult, tcu::RGBA(0,0,0,0), tcu::COMPARE_LOG_RESULT);
	isOk = isOk && tcu::pixelThresholdCompare (m_log, "Results", "Comparison result from rendering with Vertex Array Object", vaoReference, vaoResult, tcu::RGBA(0,0,0,0), tcu::COMPARE_LOG_RESULT);

	if (isOk)
	{
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		return STOP;
	}
	else
	{
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
		return STOP;
	}
}

class MultiVertexArrayObjectTest : public TestCase
{
public:

							MultiVertexArrayObjectTest	(Context& context, const char* name, const char* description);
							~MultiVertexArrayObjectTest	(void);
	virtual void			init						(void);
	virtual void			deinit						(void);
	virtual IterateResult	iterate						(void);

private:
	Spec					m_spec;
	tcu::TestLog&			m_log;
	vector<GLuint>			m_buffers;
	glu::ShaderProgram*		m_vaoProgram;
	glu::ShaderProgram*		m_stateProgram;
	de::Random				m_random;
	deUint8*				m_indices;

	void					logVertexArrayState			(tcu::TestLog& log, const VertexArrayState& state, const std::string& msg);
	deUint8*				createRandomBufferData		(const BufferSpec& buffer);
	deUint8*				generateIndices				(void);
	glu::ShaderProgram*		createProgram				(const VertexArrayState& state);
	void					setState					(const VertexArrayState& state);
	void					render						(tcu::Surface& vaoResult, tcu::Surface& defaultResult);
	void					makeDrawCall				(const VertexArrayState& state);
	void					genReferences				(tcu::Surface& vaoRef, tcu::Surface& defaultRef);

							MultiVertexArrayObjectTest	(const MultiVertexArrayObjectTest&);
	MultiVertexArrayObjectTest&	operator=				(const MultiVertexArrayObjectTest&);
};

MultiVertexArrayObjectTest::MultiVertexArrayObjectTest (Context& context, const char* name, const char* description)
	: TestCase			(context, name, description)
	, m_log				(context.getTestContext().getLog())
	, m_vaoProgram		(NULL)
	, m_stateProgram	(NULL)
	, m_random			(deStringHash(name))
	, m_indices			(NULL)
{
	// Makes zero to zero mapping for buffers
	m_buffers.push_back(0);
}

MultiVertexArrayObjectTest::~MultiVertexArrayObjectTest (void)
{
}

void MultiVertexArrayObjectTest::logVertexArrayState (tcu::TestLog& log, const VertexArrayState& state, const std::string& msg)
{
	std::stringstream message;

	message << msg << "\n";
	message << "GL_ELEMENT_ARRAY_BUFFER : " << state.elementArrayBuffer << "\n";

	for (int attribNdx = 0; attribNdx < (int)state.attributes.size(); attribNdx++)
	{
		message
		<< "attribute : " << attribNdx << "\n"
		<< "\tGL_VERTEX_ATTRIB_ARRAY_ENABLED : " << (state.attributes[attribNdx].enabled ? "GL_TRUE" : "GL_FALSE") <<  "\n"
		<< "\tGL_VERTEX_ATTRIB_ARRAY_SIZE : " << state.attributes[attribNdx].size <<  "\n"
		<< "\tGL_VERTEX_ATTRIB_ARRAY_STRIDE : " << state.attributes[attribNdx].stride <<  "\n"
		<< "\tGL_VERTEX_ATTRIB_ARRAY_TYPE : " << state.attributes[attribNdx].type <<  "\n"
		<< "\tGL_VERTEX_ATTRIB_ARRAY_NORMALIZED : " << (state.attributes[attribNdx].normalized ? "GL_TRUE" : "GL_FALSE") <<  "\n"
		<< "\tGL_VERTEX_ATTRIB_ARRAY_INTEGER : " << (state.attributes[attribNdx].integer ? "GL_TRUE" : "GL_FALSE") <<  "\n"
		<< "\tGL_VERTEX_ATTRIB_ARRAY_DIVISOR : " << state.attributes[attribNdx].divisor <<  "\n"
		<< "\tGL_VERTEX_ATTRIB_ARRAY_POINTER : " << state.attributes[attribNdx].offset <<  "\n"
		<< "\t GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING : " << m_buffers[state.attributes[attribNdx].bufferNdx] <<  "\n";
	}
	log << tcu::TestLog::Message << message.str() << tcu::TestLog::EndMessage;
}


void MultiVertexArrayObjectTest::init (void)
{
	GLint attribCount;

	GLU_CHECK_CALL(glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &attribCount));

	m_spec.useDrawElements			= false;
	m_spec.instances				= 0;
	m_spec.count					= 24;
	m_spec.indexOffset				= 0;
	m_spec.indexRangeMin			= 0;
	m_spec.indexRangeMax			= 0;
	m_spec.indexType				= GL_NONE;
	m_spec.indexCount				= 0;
	m_spec.vao.elementArrayBuffer	= 0;
	m_spec.state.elementArrayBuffer	= 0;

	for (int attribNdx = 0; attribNdx < attribCount; attribNdx++)
	{
		BufferSpec shortCoordBuffer48 = { 48, 2*384, 4, 0, 0, GL_SHORT, -32768, 32768, 0.0f, 0.0f };
		m_spec.buffers.push_back(shortCoordBuffer48);

		m_spec.state.attributes.push_back(Attribute());
		m_spec.state.attributes[attribNdx].enabled		= (m_random.getInt(0, 4) == 0) ? GL_FALSE : GL_TRUE;
		m_spec.state.attributes[attribNdx].size			= m_random.getInt(2,4);
		m_spec.state.attributes[attribNdx].stride		= 2*m_random.getInt(1, 3);
		m_spec.state.attributes[attribNdx].type			= GL_SHORT;
		m_spec.state.attributes[attribNdx].integer		= m_random.getBool();
		m_spec.state.attributes[attribNdx].divisor		= m_random.getInt(0, 1);
		m_spec.state.attributes[attribNdx].offset		= 2*m_random.getInt(0, 2);
		m_spec.state.attributes[attribNdx].normalized	= m_random.getBool();
		m_spec.state.attributes[attribNdx].bufferNdx	= attribNdx+1;

		if (attribNdx == 0)
		{
			m_spec.state.attributes[attribNdx].divisor	= 0;
			m_spec.state.attributes[attribNdx].enabled	= GL_TRUE;
			m_spec.state.attributes[attribNdx].size		= 2;
		}

		m_spec.vao.attributes.push_back(Attribute());
		m_spec.vao.attributes[attribNdx].enabled		= (m_random.getInt(0, 4) == 0) ? GL_FALSE : GL_TRUE;
		m_spec.vao.attributes[attribNdx].size			= m_random.getInt(2,4);
		m_spec.vao.attributes[attribNdx].stride			= 2*m_random.getInt(1, 3);
		m_spec.vao.attributes[attribNdx].type			= GL_SHORT;
		m_spec.vao.attributes[attribNdx].integer		= m_random.getBool();
		m_spec.vao.attributes[attribNdx].divisor		= m_random.getInt(0, 1);
		m_spec.vao.attributes[attribNdx].offset			= 2*m_random.getInt(0, 2);
		m_spec.vao.attributes[attribNdx].normalized		= m_random.getBool();
		m_spec.vao.attributes[attribNdx].bufferNdx		= attribCount - attribNdx;

		if (attribNdx == 0)
		{
			m_spec.vao.attributes[attribNdx].divisor	= 0;
			m_spec.vao.attributes[attribNdx].enabled	= GL_TRUE;
			m_spec.vao.attributes[attribNdx].size		= 2;
		}

	}

	// \note [mika] Index 0 is reserved for 0 buffer
	for (int bufferNdx = 0; bufferNdx < (int)m_spec.buffers.size(); bufferNdx++)
	{
		deUint8* data = createRandomBufferData(m_spec.buffers[bufferNdx]);

		try
		{
			GLuint buffer;
			GLU_CHECK_CALL(glGenBuffers(1, &buffer));
			m_buffers.push_back(buffer);

			GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, buffer));
			GLU_CHECK_CALL(glBufferData(GL_ARRAY_BUFFER, m_spec.buffers[bufferNdx].size, data, GL_DYNAMIC_DRAW));
			GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, 0));

		} catch (...) {
			delete[] data;
			throw;
		}

		delete[] data;
	}

	m_vaoProgram	= createProgram(m_spec.vao);
	m_log << tcu::TestLog::Message << "Program used with Vertex Array Object" << tcu::TestLog::EndMessage;
	m_log << *m_vaoProgram;
	m_stateProgram	= createProgram(m_spec.state);
	m_log << tcu::TestLog::Message << "Program used with Vertex Array State" << tcu::TestLog::EndMessage;
	m_log << *m_stateProgram;

	if (!m_vaoProgram->isOk() || !m_stateProgram->isOk())
		TCU_FAIL("Failed to compile shaders");

	if (m_spec.useDrawElements && (m_spec.vao.elementArrayBuffer == 0 || m_spec.state.elementArrayBuffer == 0))
		m_indices = generateIndices();
}

void MultiVertexArrayObjectTest::deinit (void)
{
	GLU_CHECK_CALL(glDeleteBuffers((GLsizei)m_buffers.size(), &(m_buffers[0])));
	m_buffers.clear();
	delete m_vaoProgram;
	delete m_stateProgram;
	delete[] m_indices;
}

deUint8* MultiVertexArrayObjectTest::generateIndices (void)
{
	int typeSize = 0;
	switch (m_spec.indexType)
	{
		case GL_UNSIGNED_INT:	typeSize = sizeof(GLuint);		break;
		case GL_UNSIGNED_SHORT:	typeSize = sizeof(GLushort);	break;
		case GL_UNSIGNED_BYTE:	typeSize = sizeof(GLubyte);		break;
		default:
			DE_ASSERT(false);
	}

	deUint8* indices = new deUint8[m_spec.indexCount * typeSize];

	for (int i = 0; i < m_spec.indexCount; i++)
	{
		deUint8* pos = indices + typeSize * i;

		switch (m_spec.indexType)
		{
			case GL_UNSIGNED_INT:
			{
				GLuint v = (GLuint)m_random.getInt(m_spec.indexRangeMin, m_spec.indexRangeMax);
				deMemcpy(pos, &v, sizeof(v));
				break;
			}

			case GL_UNSIGNED_SHORT:
			{
				GLushort v = (GLushort)m_random.getInt(m_spec.indexRangeMin, m_spec.indexRangeMax);
				deMemcpy(pos, &v, sizeof(v));
				break;
			}

			case GL_UNSIGNED_BYTE:
			{
				GLubyte v = (GLubyte)m_random.getInt(m_spec.indexRangeMin, m_spec.indexRangeMax);
				deMemcpy(pos, &v, sizeof(v));
				break;
			}

			default:
				DE_ASSERT(false);
		}
	}

	return indices;
}

deUint8* MultiVertexArrayObjectTest::createRandomBufferData (const BufferSpec& buffer)
{
	deUint8* data = new deUint8[buffer.size];

	int stride;

	if (buffer.stride != 0)
	{
		stride = buffer.stride;
	}
	else
	{
		switch (buffer.type)
		{
			case GL_FLOAT:			stride = buffer.componentCount * (int)sizeof(GLfloat);	break;
			case GL_INT:			stride = buffer.componentCount * (int)sizeof(GLint);	break;
			case GL_UNSIGNED_INT:	stride = buffer.componentCount * (int)sizeof(GLuint);	break;
			case GL_SHORT:			stride = buffer.componentCount * (int)sizeof(GLshort);	break;
			case GL_UNSIGNED_SHORT:	stride = buffer.componentCount * (int)sizeof(GLushort);	break;
			case GL_BYTE:			stride = buffer.componentCount * (int)sizeof(GLbyte);	break;
			case GL_UNSIGNED_BYTE:	stride = buffer.componentCount * (int)sizeof(GLubyte);	break;

			default:
				stride = 0;
				DE_ASSERT(DE_FALSE);
		}
	}

	deUint8* itr = data;

	for (int pos = 0; pos < buffer.count; pos++)
	{
		deUint8* componentItr = itr;
		for (int componentNdx = 0; componentNdx < buffer.componentCount; componentNdx++)
		{
			switch (buffer.type)
			{
				case GL_FLOAT:
				{
					float v = buffer.floatRangeMin + (buffer.floatRangeMax - buffer.floatRangeMin) * m_random.getFloat();
					deMemcpy(componentItr, &v, sizeof(v));
					componentItr += sizeof(v);
					break;
				}

				case GL_INT:
				{
					GLint v = m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
					deMemcpy(componentItr, &v, sizeof(v));
					componentItr += sizeof(v);
					break;
				}

				case GL_UNSIGNED_INT:
				{
					GLuint v = (GLuint)m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
					deMemcpy(componentItr, &v, sizeof(v));
					componentItr += sizeof(v);
					break;
				}

				case GL_SHORT:
				{
					GLshort v = (GLshort)m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
					deMemcpy(componentItr, &v, sizeof(v));
					componentItr += sizeof(v);
					break;
				}

				case GL_UNSIGNED_SHORT:
				{
					GLushort v = (GLushort)m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
					deMemcpy(componentItr, &v, sizeof(v));
					componentItr += sizeof(v);
					break;
				}

				case GL_BYTE:
				{
					GLbyte v = (GLbyte)m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
					deMemcpy(componentItr, &v, sizeof(v));
					componentItr += sizeof(v);
					break;
				}

				case GL_UNSIGNED_BYTE:
				{
					GLubyte v = (GLubyte)m_random.getInt(buffer.intRangeMin, buffer.intRangeMax);
					deMemcpy(componentItr, &v, sizeof(v));
					componentItr += sizeof(v);
					break;
				}

				default:
					DE_ASSERT(false);
			}
		}

		itr += stride;
	}

	return data;
}

glu::ShaderProgram* MultiVertexArrayObjectTest::createProgram (const VertexArrayState& state)
{
	std::stringstream vertexShaderStream;
	std::stringstream value;

	vertexShaderStream << "#version 300 es\n";

	for (int attribNdx = 0; attribNdx < (int)state.attributes.size(); attribNdx++)
	{
		if (state.attributes[attribNdx].integer)
			vertexShaderStream << "layout(location = " << attribNdx << ") in mediump ivec4 a_attrib" << attribNdx << ";\n";
		else
			vertexShaderStream << "layout(location = " << attribNdx << ") in mediump vec4 a_attrib" << attribNdx << ";\n";

		if (state.attributes[attribNdx].integer)
		{
			float scale = 0.0f;

			switch (state.attributes[0].type)
			{
				case GL_SHORT:			scale  = (1.0f/float((1u<<14)-1u));	break;
				case GL_UNSIGNED_SHORT:	scale  = (1.0f/float((1u<<15)-1u));	break;
				case GL_INT:			scale  = (1.0f/float((1u<<30)-1u));	break;
				case GL_UNSIGNED_INT:	scale  = (1.0f/float((1u<<31)-1u));	break;
				case GL_BYTE:			scale  = (1.0f/float((1u<<6)-1u));	break;
				case GL_UNSIGNED_BYTE:	scale  = (1.0f/float((1u<<7)-1u));	break;

				default:
					DE_ASSERT(DE_FALSE);
			}
			value << (attribNdx != 0 ? " + " : "" ) << scale << " * vec4(a_attrib" << attribNdx << ")";
		}
		else if (state.attributes[attribNdx].type != GL_FLOAT && !state.attributes[attribNdx].normalized)
		{
			float scale = 0.0f;

			switch (state.attributes[0].type)
			{
				case GL_SHORT:			scale  = (0.5f/float((1u<<14)-1u));	break;
				case GL_UNSIGNED_SHORT:	scale  = (0.5f/float((1u<<15)-1u));	break;
				case GL_INT:			scale  = (0.5f/float((1u<<30)-1u));	break;
				case GL_UNSIGNED_INT:	scale  = (0.5f/float((1u<<31)-1u));	break;
				case GL_BYTE:			scale  = (0.5f/float((1u<<6)-1u));	break;
				case GL_UNSIGNED_BYTE:	scale  = (0.5f/float((1u<<7)-1u));	break;

				default:
					DE_ASSERT(DE_FALSE);
			}
			value << (attribNdx != 0 ? " + " : "" ) << scale << " * a_attrib" << attribNdx;
		}
		else
			value << (attribNdx != 0 ? " + " : "" ) << "a_attrib" << attribNdx;
	}

	vertexShaderStream
		<< "out mediump vec4 v_value;\n"
		<< "void main (void)\n"
		<< "{\n"
		<< "\tv_value = " << value.str() << ";\n";

	if (state.attributes[0].integer)
	{
		float scale = 0.0f;

		switch (state.attributes[0].type)
		{
			case GL_SHORT:			scale  = (1.0f/float((1u<<14)-1u));	break;
			case GL_UNSIGNED_SHORT:	scale  = (1.0f/float((1u<<15)-1u));	break;
			case GL_INT:			scale  = (1.0f/float((1u<<30)-1u));	break;
			case GL_UNSIGNED_INT:	scale  = (1.0f/float((1u<<31)-1u));	break;
			case GL_BYTE:			scale  = (1.0f/float((1u<<6)-1u));	break;
			case GL_UNSIGNED_BYTE:	scale  = (1.0f/float((1u<<7)-1u));	break;


			default:
				DE_ASSERT(DE_FALSE);
		}

		vertexShaderStream
			<< "\tgl_Position = vec4(" << scale << " * " <<  "a_attrib0.xyz, 1.0);\n"
			<< "}";
	}
	else
	{
		if (state.attributes[0].normalized || state.attributes[0].type == GL_FLOAT)
		{
			vertexShaderStream
				<< "\tgl_Position = vec4(a_attrib0.xyz, 1.0);\n"
				<< "}";
		}
		else
		{
			float scale = 0.0f;

			switch (state.attributes[0].type)
			{
				case GL_SHORT:			scale  = (1.0f/float((1u<<14)-1u));	break;
				case GL_UNSIGNED_SHORT:	scale  = (1.0f/float((1u<<15)-1u));	break;
				case GL_INT:			scale  = (1.0f/float((1u<<30)-1u));	break;
				case GL_UNSIGNED_INT:	scale  = (1.0f/float((1u<<31)-1u));	break;
				case GL_BYTE:			scale  = (1.0f/float((1u<<6)-1u));	break;
				case GL_UNSIGNED_BYTE:	scale  = (1.0f/float((1u<<7)-1u));	break;

				default:
					DE_ASSERT(DE_FALSE);
			}

			scale *= 0.5f;

			vertexShaderStream
				<< "\tgl_Position = vec4(" << scale << " * " <<  "vec3(a_attrib0.xyz), 1.0);\n"
				<< "}";
		}
	}

	const char* fragmentShader =
	"#version 300 es\n"
	"in mediump vec4 v_value;\n"
	"layout(location = 0) out mediump vec4 fragColor;\n"
	"void main (void)\n"
	"{\n"
	"\tfragColor = vec4(v_value.xyz, 1.0);\n"
	"}";

	return new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderStream.str(), fragmentShader));
}

void MultiVertexArrayObjectTest::setState (const VertexArrayState& state)
{
	GLU_CHECK_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_buffers[state.elementArrayBuffer]));

	for (int attribNdx = 0; attribNdx < (int)state.attributes.size(); attribNdx++)
	{
		GLU_CHECK_CALL(glBindBuffer(GL_ARRAY_BUFFER, m_buffers[state.attributes[attribNdx].bufferNdx]));
		if (state.attributes[attribNdx].enabled)
			GLU_CHECK_CALL(glEnableVertexAttribArray(attribNdx));
		else
			GLU_CHECK_CALL(glDisableVertexAttribArray(attribNdx));

		if (state.attributes[attribNdx].integer)
			GLU_CHECK_CALL(glVertexAttribIPointer(attribNdx, state.attributes[attribNdx].size, state.attributes[attribNdx].type, state.attributes[attribNdx].stride, (const GLvoid*)((GLintptr)state.attributes[attribNdx].offset)));
		else
			GLU_CHECK_CALL(glVertexAttribPointer(attribNdx, state.attributes[attribNdx].size, state.attributes[attribNdx].type, state.attributes[attribNdx].normalized, state.attributes[attribNdx].stride, (const GLvoid*)((GLintptr)state.attributes[attribNdx].offset)));

		GLU_CHECK_CALL(glVertexAttribDivisor(attribNdx, state.attributes[attribNdx].divisor));
	}
}

void MultiVertexArrayObjectTest::makeDrawCall (const VertexArrayState& state)
{
	GLU_CHECK_CALL(glClearColor(0.7f, 0.7f, 0.7f, 1.0f));
	GLU_CHECK_CALL(glClear(GL_COLOR_BUFFER_BIT));

	if (m_spec.useDrawElements)
	{
		if (state.elementArrayBuffer == 0)
		{
			if (m_spec.instances == 0)
				GLU_CHECK_CALL(glDrawElements(GL_TRIANGLES, m_spec.count, m_spec.indexType, m_indices));
			else
				GLU_CHECK_CALL(glDrawElementsInstanced(GL_TRIANGLES, m_spec.count, m_spec.indexType, m_indices, m_spec.instances));
		}
		else
		{
			if (m_spec.instances == 0)
				GLU_CHECK_CALL(glDrawElements(GL_TRIANGLES, m_spec.count, m_spec.indexType, (GLvoid*)((GLintptr)m_spec.indexOffset)));
			else
				GLU_CHECK_CALL(glDrawElementsInstanced(GL_TRIANGLES, m_spec.count, m_spec.indexType, (GLvoid*)((GLintptr)m_spec.indexOffset), m_spec.instances));
		}
	}
	else
	{
		if (m_spec.instances == 0)
			GLU_CHECK_CALL(glDrawArrays(GL_TRIANGLES, 0, m_spec.count));
		else
			GLU_CHECK_CALL(glDrawArraysInstanced(GL_TRIANGLES, 0, m_spec.count, m_spec.instances));
	}
}

void MultiVertexArrayObjectTest::render (tcu::Surface& vaoResult, tcu::Surface& defaultResult)
{
	GLuint vao = 0;

	GLU_CHECK_CALL(glGenVertexArrays(1, &vao));
	GLU_CHECK_CALL(glBindVertexArray(vao));
	setState(m_spec.vao);
	GLU_CHECK_CALL(glBindVertexArray(0));

	setState(m_spec.state);

	GLU_CHECK_CALL(glBindVertexArray(vao));
	GLU_CHECK_CALL(glUseProgram(m_vaoProgram->getProgram()));
	makeDrawCall(m_spec.vao);
	glu::readPixels(m_context.getRenderContext(), 0, 0, vaoResult.getAccess());
	setState(m_spec.vao);
	GLU_CHECK_CALL(glBindVertexArray(0));

	GLU_CHECK_CALL(glUseProgram(m_stateProgram->getProgram()));
	makeDrawCall(m_spec.state);
	glu::readPixels(m_context.getRenderContext(), 0, 0, defaultResult.getAccess());
}

void MultiVertexArrayObjectTest::genReferences (tcu::Surface& vaoRef, tcu::Surface& defaultRef)
{
	setState(m_spec.vao);
	GLU_CHECK_CALL(glUseProgram(m_vaoProgram->getProgram()));
	makeDrawCall(m_spec.vao);
	glu::readPixels(m_context.getRenderContext(), 0, 0, vaoRef.getAccess());

	setState(m_spec.state);
	GLU_CHECK_CALL(glUseProgram(m_stateProgram->getProgram()));
	makeDrawCall(m_spec.state);
	glu::readPixels(m_context.getRenderContext(), 0, 0, defaultRef.getAccess());
}

TestCase::IterateResult MultiVertexArrayObjectTest::iterate (void)
{
	tcu::Surface	vaoReference	(m_context.getRenderContext().getRenderTarget().getWidth(), m_context.getRenderContext().getRenderTarget().getHeight());
	tcu::Surface	stateReference	(m_context.getRenderContext().getRenderTarget().getWidth(), m_context.getRenderContext().getRenderTarget().getHeight());

	tcu::Surface	vaoResult		(m_context.getRenderContext().getRenderTarget().getWidth(), m_context.getRenderContext().getRenderTarget().getHeight());
	tcu::Surface	stateResult		(m_context.getRenderContext().getRenderTarget().getWidth(), m_context.getRenderContext().getRenderTarget().getHeight());

	bool			isOk;

	logVertexArrayState(m_log, m_spec.vao, "Vertex Array Object State");
	logVertexArrayState(m_log, m_spec.state, "OpenGL Vertex Array State");
	genReferences(stateReference, vaoReference);
	render(stateResult, vaoResult);

	isOk = tcu::pixelThresholdCompare (m_log, "Results", "Comparison result from rendering with Vertex Array State", stateReference, stateResult, tcu::RGBA(0,0,0,0), tcu::COMPARE_LOG_RESULT);
	isOk = isOk && tcu::pixelThresholdCompare (m_log, "Results", "Comparison result from rendering with Vertex Array Object", vaoReference, vaoResult, tcu::RGBA(0,0,0,0), tcu::COMPARE_LOG_RESULT);

	if (isOk)
	{
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		return STOP;
	}
	else
	{
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
		return STOP;
	}
}

VertexArrayObjectTestGroup::VertexArrayObjectTestGroup (Context& context)
	: TestCaseGroup(context, "vertex_array_objects", "Vertex array object test cases")
{
}

VertexArrayObjectTestGroup::~VertexArrayObjectTestGroup (void)
{
}

void VertexArrayObjectTestGroup::init (void)
{
	BufferSpec floatCoordBuffer48_1 = { 48, 384, 2, 0, 0, GL_FLOAT, 0, 0, -1.0f, 1.0f };
	BufferSpec floatCoordBuffer48_2 = { 48, 384, 2, 0, 0, GL_FLOAT, 0, 0, -1.0f, 1.0f };

	BufferSpec shortCoordBuffer48 = { 48, 192, 2, 0, 0, GL_SHORT, -32768, 32768, 0.0f, 0.0f };

	// Different buffer
	{
		Spec spec;

		VertexArrayState state;

		state.attributes.push_back(Attribute());

		state.attributes[0].enabled		= true;
		state.attributes[0].size		= 2;
		state.attributes[0].stride		= 0;
		state.attributes[0].type		= GL_FLOAT;
		state.attributes[0].integer		= GL_FALSE;
		state.attributes[0].divisor		= 0;
		state.attributes[0].offset		= 0;
		state.attributes[0].normalized	= GL_FALSE;

		state.elementArrayBuffer = 0;

		spec.buffers.push_back(floatCoordBuffer48_1);
		spec.buffers.push_back(floatCoordBuffer48_2);

		spec.useDrawElements	= false;
		spec.instances			= 0;
		spec.count				= 48;
		spec.vao				= state;
		spec.state				= state;
		spec.indexOffset		= 0;
		spec.indexRangeMin		= 0;
		spec.indexRangeMax		= 0;
		spec.indexType			= GL_NONE;
		spec.indexCount			= 0;

		spec.state.attributes[0].bufferNdx	= 1;
		spec.vao.attributes[0].bufferNdx	= 2;
		addChild(new VertexArrayObjectTest(m_context, spec, "diff_buffer", "diff_buffer"));
	}
	// Different size
	{
		Spec spec;

		VertexArrayState state;

		state.attributes.push_back(Attribute());

		state.attributes[0].enabled		= true;
		state.attributes[0].size		= 2;
		state.attributes[0].stride		= 0;
		state.attributes[0].type		= GL_FLOAT;
		state.attributes[0].integer		= GL_FALSE;
		state.attributes[0].divisor		= 0;
		state.attributes[0].offset		= 0;
		state.attributes[0].normalized	= GL_FALSE;
		state.attributes[0].bufferNdx	= 1;

		state.elementArrayBuffer = 0;

		spec.buffers.push_back(floatCoordBuffer48_1);

		spec.useDrawElements	= false;
		spec.instances			= 0;
		spec.count				= 24;
		spec.vao				= state;
		spec.state				= state;
		spec.indexOffset		= 0;
		spec.indexRangeMin		= 0;
		spec.indexRangeMax		= 0;
		spec.indexType			= GL_NONE;
		spec.indexCount			= 0;

		spec.state.attributes[0].size		= 2;
		spec.vao.attributes[0].size			= 3;
		addChild(new VertexArrayObjectTest(m_context, spec, "diff_size", "diff_size"));
	}

	// Different stride
	{
		Spec spec;

		VertexArrayState state;

		state.attributes.push_back(Attribute());

		state.attributes[0].enabled		= true;
		state.attributes[0].size		= 2;
		state.attributes[0].stride		= 0;
		state.attributes[0].type		= GL_SHORT;
		state.attributes[0].integer		= GL_FALSE;
		state.attributes[0].divisor		= 0;
		state.attributes[0].offset		= 0;
		state.attributes[0].normalized	= GL_TRUE;
		state.attributes[0].bufferNdx	= 1;

		state.elementArrayBuffer = 0;

		spec.buffers.push_back(shortCoordBuffer48);

		spec.useDrawElements	= false;
		spec.instances			= 0;
		spec.count				= 24;
		spec.vao				= state;
		spec.state				= state;
		spec.indexOffset		= 0;
		spec.indexRangeMin		= 0;
		spec.indexRangeMax		= 0;
		spec.indexType			= GL_NONE;
		spec.indexCount			= 0;

		spec.vao.attributes[0].stride	= 2;
		spec.state.attributes[0].stride	= 4;
		addChild(new VertexArrayObjectTest(m_context, spec, "diff_stride", "diff_stride"));
	}

	// Different types
	{
		Spec spec;

		VertexArrayState state;

		state.attributes.push_back(Attribute());

		state.attributes[0].enabled		= true;
		state.attributes[0].size		= 2;
		state.attributes[0].stride		= 0;
		state.attributes[0].type		= GL_SHORT;
		state.attributes[0].integer		= GL_FALSE;
		state.attributes[0].divisor		= 0;
		state.attributes[0].offset		= 0;
		state.attributes[0].normalized	= GL_TRUE;
		state.attributes[0].bufferNdx	= 1;

		state.elementArrayBuffer = 0;

		spec.buffers.push_back(shortCoordBuffer48);

		spec.useDrawElements	= false;
		spec.instances			= 0;
		spec.count				= 24;
		spec.vao				= state;
		spec.state				= state;
		spec.indexOffset		= 0;
		spec.indexRangeMin		= 0;
		spec.indexRangeMax		= 0;
		spec.indexType			= GL_NONE;
		spec.indexCount			= 0;

		spec.vao.attributes[0].type		= GL_SHORT;
		spec.state.attributes[0].type	= GL_BYTE;
		addChild(new VertexArrayObjectTest(m_context, spec, "diff_type", "diff_type"));
	}
	// Different "integer"
	{
		Spec spec;

		VertexArrayState state;

		state.attributes.push_back(Attribute());

		state.attributes[0].enabled		= true;
		state.attributes[0].size		= 2;
		state.attributes[0].stride		= 0;
		state.attributes[0].type		= GL_BYTE;
		state.attributes[0].integer		= GL_TRUE;
		state.attributes[0].divisor		= 0;
		state.attributes[0].offset		= 0;
		state.attributes[0].normalized	= GL_FALSE;
		state.attributes[0].bufferNdx	= 1;

		state.elementArrayBuffer = 0;

		spec.buffers.push_back(shortCoordBuffer48);

		spec.useDrawElements	= false;
		spec.count				= 24;
		spec.vao				= state;
		spec.state				= state;
		spec.instances			= 0;
		spec.indexOffset		= 0;
		spec.indexRangeMin		= 0;
		spec.indexRangeMax		= 0;
		spec.indexType			= GL_NONE;
		spec.indexCount			= 0;

		spec.state.attributes[0].integer	= GL_FALSE;
		spec.vao.attributes[0].integer		= GL_TRUE;
		addChild(new VertexArrayObjectTest(m_context, spec, "diff_integer", "diff_integer"));
	}
	// Different divisor
	{
		Spec spec;

		VertexArrayState state;

		state.attributes.push_back(Attribute());
		state.attributes.push_back(Attribute());

		state.attributes[0].enabled		= true;
		state.attributes[0].size		= 2;
		state.attributes[0].stride		= 0;
		state.attributes[0].type		= GL_SHORT;
		state.attributes[0].integer		= GL_FALSE;
		state.attributes[0].divisor		= 0;
		state.attributes[0].offset		= 0;
		state.attributes[0].normalized	= GL_TRUE;
		state.attributes[0].bufferNdx	= 1;

		state.attributes[1].enabled		= true;
		state.attributes[1].size		= 4;
		state.attributes[1].stride		= 0;
		state.attributes[1].type		= GL_FLOAT;
		state.attributes[1].integer		= GL_FALSE;
		state.attributes[1].divisor		= 0;
		state.attributes[1].offset		= 0;
		state.attributes[1].normalized	= GL_FALSE;
		state.attributes[1].bufferNdx	= 2;

		state.elementArrayBuffer = 0;

		spec.buffers.push_back(shortCoordBuffer48);
		spec.buffers.push_back(floatCoordBuffer48_1);

		spec.useDrawElements	= false;
		spec.instances			= 10;
		spec.count				= 12;
		spec.vao				= state;
		spec.state				= state;
		spec.indexOffset		= 0;
		spec.indexRangeMin		= 0;
		spec.indexRangeMax		= 0;
		spec.indexType			= GL_NONE;
		spec.indexCount			= 0;

		spec.vao.attributes[1].divisor		= 3;
		spec.state.attributes[1].divisor	= 2;

		addChild(new VertexArrayObjectTest(m_context, spec, "diff_divisor", "diff_divisor"));
	}
	// Different offset
	{
		Spec spec;

		VertexArrayState state;

		state.attributes.push_back(Attribute());

		state.attributes[0].enabled		= true;
		state.attributes[0].size		= 2;
		state.attributes[0].stride		= 0;
		state.attributes[0].type		= GL_SHORT;
		state.attributes[0].integer		= GL_FALSE;
		state.attributes[0].divisor		= 0;
		state.attributes[0].offset		= 0;
		state.attributes[0].normalized	= GL_TRUE;
		state.attributes[0].bufferNdx	= 1;

		state.elementArrayBuffer = 0;

		spec.buffers.push_back(shortCoordBuffer48);

		spec.useDrawElements	= false;
		spec.instances			= 0;
		spec.count				= 24;
		spec.vao				= state;
		spec.state				= state;
		spec.indexOffset		= 0;
		spec.indexRangeMin		= 0;
		spec.indexRangeMax		= 0;
		spec.indexType			= GL_NONE;
		spec.indexCount			= 0;

		spec.vao.attributes[0].offset	= 2;
		spec.state.attributes[0].offset	= 4;
		addChild(new VertexArrayObjectTest(m_context, spec, "diff_offset", "diff_offset"));
	}
	// Different normalize
	{
		Spec spec;

		VertexArrayState state;

		state.attributes.push_back(Attribute());

		state.attributes[0].enabled		= true;
		state.attributes[0].size		= 2;
		state.attributes[0].stride		= 0;
		state.attributes[0].type		= GL_SHORT;
		state.attributes[0].integer		= GL_FALSE;
		state.attributes[0].divisor		= 0;
		state.attributes[0].offset		= 0;
		state.attributes[0].normalized	= GL_TRUE;
		state.attributes[0].bufferNdx	= 1;

		state.elementArrayBuffer = 0;

		spec.buffers.push_back(shortCoordBuffer48);

		spec.useDrawElements	= false;
		spec.instances			= 0;
		spec.count				= 48;
		spec.vao				= state;
		spec.state				= state;
		spec.indexOffset		= 0;
		spec.indexRangeMin		= 0;
		spec.indexRangeMax		= 0;
		spec.indexType			= GL_NONE;
		spec.indexCount			= 0;

		spec.vao.attributes[0].normalized	= GL_TRUE;
		spec.state.attributes[0].normalized	= GL_FALSE;
		addChild(new VertexArrayObjectTest(m_context, spec, "diff_normalize", "diff_normalize"));
	}
	// DrawElements with buffer / Pointer
	{
		Spec spec;

		VertexArrayState state;

		state.attributes.push_back(Attribute());

		state.attributes[0].enabled		= true;
		state.attributes[0].size		= 2;
		state.attributes[0].stride		= 0;
		state.attributes[0].type		= GL_FLOAT;
		state.attributes[0].integer		= GL_FALSE;
		state.attributes[0].divisor		= 0;
		state.attributes[0].offset		= 0;
		state.attributes[0].normalized	= GL_TRUE;
		state.attributes[0].bufferNdx	= 1;

		state.elementArrayBuffer = 0;

		spec.buffers.push_back(floatCoordBuffer48_1);

		BufferSpec indexBuffer = { 24, 192, 1, 0, 0, GL_UNSIGNED_SHORT, 0, 48, 0.0f, 0.0f };
		spec.buffers.push_back(indexBuffer);

		spec.useDrawElements	= true;
		spec.count				= 24;
		spec.vao				= state;
		spec.state				= state;
		spec.instances			= 0;
		spec.indexOffset		= 0;
		spec.indexRangeMin		= 0;
		spec.indexRangeMax		= 48;
		spec.indexType			= GL_UNSIGNED_SHORT;
		spec.indexCount			= 24;

		spec.state.elementArrayBuffer	= 0;
		spec.vao.elementArrayBuffer		= 2;
		addChild(new VertexArrayObjectTest(m_context, spec, "diff_indices", "diff_indices"));
	}
	// Use all attributes

	addChild(new MultiVertexArrayObjectTest(m_context, "all_attributes", "all_attributes"));
}

} // Functional
} // gles3
} // deqp
